﻿using Microsoft.EntityFrameworkCore;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using System.Security.Cryptography;
using System.Text.Json.Serialization;

namespace EasyDDD.Domain.User
{
    public class ApplicationUser : DomainEntity<Guid>, IAggregateRoot
    {
        [JsonConstructor]
        protected ApplicationUser() { }

        #region 属性
        /// <summary>
        /// Gets or sets the user name for this user.
        /// </summary>
        [StringLength(100)]
        public virtual string UserName { get; set; } = string.Empty;

        /// <summary>
        /// Gets or sets the normalized user name for this user.
        /// </summary>
        [StringLength(100)]
        public virtual string NormalizedUserName { get; set; } = string.Empty;

        /// <summary>
        /// Gets or sets the email address for this user.
        /// </summary>
        [StringLength(100)]
        public virtual string Email { get; set; } = string.Empty;

        /// <summary>
        /// Gets or sets the normalized email address for this user.
        /// </summary>
        [StringLength(100)]
        public virtual string NormalizedEmail { get; set; } = string.Empty;

        /// <summary>
        /// Gets or sets a flag indicating if a user has confirmed their email address.
        /// </summary>
        /// <value>True if the email address has been confirmed, otherwise false.</value>
        public virtual bool EmailConfirmed { get; set; }

        /// <summary>
        /// Gets or sets a salted and hashed representation of the password for this user.
        /// </summary>
        [StringLength(100)]
        public virtual string PasswordHash { get; set; } = string.Empty;

        [StringLength(50)]
        public virtual string PasswordSalt { get; set; } = string.Empty;


        /// <summary>
        /// Gets or sets a telephone number for the user.
        /// </summary>
        [StringLength(20)]
        public virtual string PhoneNumber { get; set; } = string.Empty;

        /// <summary>
        /// Gets or sets a flag indicating if a user has confirmed their telephone address.
        /// </summary>
        /// <value>True if the telephone number has been confirmed, otherwise false.</value>
        public virtual bool PhoneNumberConfirmed { get; set; }

        /// <summary>
        /// Gets or sets a flag indicating if two factor authentication is enabled for this user.
        /// </summary>
        /// <value>True if 2fa is enabled, otherwise false.</value>
        public virtual bool TwoFactorEnabled { get; set; }

        /// <summary>
        /// Gets or sets the date and time, in UTC, when any user lockout ends.
        /// </summary>
        /// <remarks>
        /// A value in the past means the user is not locked out.
        /// </remarks>
        public virtual DateTimeOffset LockoutEnd { get; set; } = DateTimeOffset.Now;

        /// <summary>
        /// Gets or sets a flag indicating if the user could be locked out.
        /// 根据LockoutEnd判断是否锁定，感觉没啥用，观察一段时间看看是否去掉
        /// </summary>
        /// <value>True if the user could be locked out, otherwise false.</value>
        public virtual bool LockoutEnabled { get; set; }

        /// <summary>
        /// Gets or sets the number of failed login attempts for the current user.
        /// </summary>
        public virtual int AccessFailedCount { get; set; }

        /// <summary>
        /// 状态 0.正常 1.停用 2.待审核 3.锁定
        /// </summary>
        [Comment("账户状态")]
        public virtual short Status { get; set; } = 0;

        /// <summary>
        /// 用户类型 
        /// </summary>
        [Comment("用户类型")]
        public virtual int UserType { get; set; } = 0;

        /// <summary>
        /// 昵称
        /// </summary>
        [Comment("昵称")]
        public virtual string NickName { get; set; } = string.Empty;

        /// <summary>
        /// 用于登录刷新的Token
        /// </summary>
        [Comment("RefreshToken")]
        [StringLength(512)]
        public virtual string RefreshToken { get; set; } = string.Empty;

        /// <summary>
        /// 最后登录IP
        /// </summary>
        [Comment("最后登录IP")]
        [StringLength(30)]
        public virtual string LastIp { get; set; } = string.Empty;

        /// <summary>
        /// 最后登录时间
        /// </summary>
        [Comment("最后登录时间")]
        public DateTimeOffset LastTime { get; set; } = DateTimeOffset.Now;

        /// <summary>
        /// 备注
        /// </summary>
        [Comment("备注")]
        [StringLength(500)]
        public string Remark { get; set; } = default!;

        #endregion

        #region 方法
        public override void Add()
        {
            if (this.Id != Guid.Empty)
            {
                throw new InvalidOperationException($"该实体{this.GetType().Name}:{this.Id}已创建！");
            }

            if (string.IsNullOrEmpty(UserName)) throw new ArgumentNullException("UserName不能为空");
            if (string.IsNullOrEmpty(PasswordHash)) throw new ArgumentNullException("PasswordHash不能为空");

            NormalizedUserName = UserName.ToUpper();
            NormalizedEmail = Email.ToUpper();
        }

        public virtual void Add(string userName)
        {
            UserName = userName;
            Add();
        }
        public virtual void Add(string userName, string passwordHash)
        {
            UserName = userName;
            PasswordHash = passwordHash;
            Add();
        }

        public virtual void Set(string phoneNumber, string userName, string nickName, string remark)
        {
            if (PhoneNumber != phoneNumber) PhoneNumber = phoneNumber;
            if (UserName != userName) UserName = userName;
            if (NickName != nickName) NickName = nickName;
            if (Remark != remark) Remark = remark;
        }

        public virtual void SetPassword(string password)
        {
            this.PasswordSalt = Convert.ToBase64String(Guid.NewGuid().ToByteArray());
            {
                //using var tripleDes = TripleDES.Create();
                //tripleDes.Key = System.Text.Encoding.Default.GetBytes(this.PasswordSalt);
                //tripleDes.IV = System.Text.Encoding.Default.GetBytes(GetType().Name);
                //tripleDes.Mode = CipherMode.CBC;
                //tripleDes.Padding = PaddingMode.Zeros;

                //using var transform = tripleDes.CreateDecryptor();
                //var encryptedData = System.Text.Encoding.Default.GetBytes(password);
                //var result = transform.TransformFinalBlock(encryptedData, 0, encryptedData.Length);
                //var passwordHash = Convert.ToBase64String(result);

                //if (PasswordHash != passwordHash)
                //{
                //    PasswordHash = passwordHash;
                //}
            }

            var result = MD5.Create().ComputeHash(System.Text.Encoding.Default.GetBytes(UserName + password + PasswordSalt));
            var passwordHash = Convert.ToBase64String(result);
            if (PasswordHash != passwordHash)
            {
                PasswordHash = passwordHash;
            }

        }

        public virtual bool ComparePassword(string password)
        {
            //using var tripleDes = TripleDES.Create();
            //tripleDes.Key = System.Text.Encoding.Default.GetBytes(this.PasswordSalt);
            //tripleDes.IV = System.Text.Encoding.Default.GetBytes(GetType().Name);
            //tripleDes.Mode = CipherMode.CBC;
            //tripleDes.Padding = PaddingMode.Zeros;

            //using var transform = tripleDes.CreateDecryptor();
            //var encryptedData = System.Text.Encoding.Default.GetBytes(password);
            //var result = transform.TransformFinalBlock(encryptedData, 0, encryptedData.Length);
            //var passwordHash = Convert.ToBase64String(result);

            var result = MD5.Create().ComputeHash(System.Text.Encoding.Default.GetBytes(UserName + password + PasswordSalt));
            var passwordHash = Convert.ToBase64String(result);
            return this.PasswordHash == passwordHash;
        }

        public virtual bool IsLockout()
        {
            if (this.LockoutEnd > DateTimeOffset.Now)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public virtual void LoginFail()
        {
            this.AccessFailedCount++;
            if (this.AccessFailedCount >= 10)
            {
                //this.LockoutEnabled = true;
                this.LockoutEnd = DateTimeOffset.Now.AddMinutes(10);
            }
        }
        public virtual void LoginSuccess()
        {
            this.LastIp = string.Empty;
            this.LastTime = DateTimeOffset.UtcNow;
            if (this.AccessFailedCount > 0)
            {
                this.AccessFailedCount = 0;
            }
        }

        #endregion

        public virtual ICollection<ApplicationUserClaim> Claims { get; set; } = new Collection<ApplicationUserClaim>();
        public virtual ICollection<ApplicationUserLogin> Logins { get; set; } = new Collection<ApplicationUserLogin>();
        public virtual ICollection<ApplicationUserToken> Tokens { get; set; } = new Collection<ApplicationUserToken>();
        public virtual ICollection<ApplicationUserRole> UserRoles { get; set; } = new Collection<ApplicationUserRole>();
        public virtual void SetUserRole(Guid roleId)
        {
            if (this.UserRoles.Any(e => e.UserId == this.Id && e.RoleId == roleId))
            {
                return;
            }
            this.UserRoles.Add(new ApplicationUserRole()
            {
                UserId = this.Id,
                RoleId = roleId
            });
        }

        public virtual void RomoveUserRole(Guid roleId)
        {
            var item = this.UserRoles.FirstOrDefault(e => e.UserId == this.Id && e.RoleId == roleId);
            if (item != null)
            {
                this.UserRoles.Remove(item);
            }
        }

        public virtual void SetUserRoleList(Guid[] roleIds)
        {
            var dels = this.UserRoles.Where(e => e.UserId == this.Id && !roleIds.Contains(e.RoleId)).ToList();
            foreach (var item in dels)
            {
                this.RomoveUserRole(item.RoleId);
            }
            foreach (var roleId in roleIds)
            {
                this.SetUserRole(roleId);
            }
        }
    }
}

